<?php
use Doctrine\ORM\EntityRepository,
    Doctrine\ORM\QueryBuilder,
    Doctrine\ORM\EntityManager,
    Doctrine\ORM\Query,
    Doctrine\ORM\Query\AST,
    Doctrine\DBAL\Types\Type,
    Doctrine\ORM\Mapping\ClassMetadata,
    Doctrine\ORM\NonUniqueResultException,
    Doctrine\ORM\Query\AST\Functions,
    Doctrine\ORM\Query\AST\Node;
    
class ZendX_Extjs_View_Helper_DoctrineGrid extends ZendX_Extjs_View_Helper_Base
{
	public function doctrineGrid($gridName, $qb, $crud = true, array $storeConfig = array(), array $gridConfig = array())
	{	
		$cols = $this->buildFields($qb);
		$fields = array();
		foreach ($cols as $col)
		{
			$fields[] = $col['dataIndex'];
		}
		
		$controllername = Zend_Controller_Front::getInstance()->getRequest()->getControllerName();		

		$storeUrl = array_key_exists('url', $storeConfig)? $storeConfig['url'] : "/{$controllername}/gridjson";
		$storeConf = array(
			'storeId'=>"{$gridName}_store",
			'fields'=>$fields,
			'autoLoad'=>true,
			'pageSize'=> 10,
			'proxy' => array(
				'type'=>'ajax',
				'url'=>$storeUrl,				
				'reader'=>array(
					'type'=>'json',
					'root'=>'source',
					'totalProperty'=>'totalCount'
				),
			)
		);
		
		$storeConf = array_merge($storeConf, $storeConfig);
		
		$store = ZendX_Extjs_View_Helper_ExtComponent::js('Ext.data.Store', $storeConf, "{$gridName}_store");		
		$this->extjs->addOnLoad(Zend_Json::prettyPrint($store, array("indent" => " ")));
		
		
		$gridConf = array(			
			'store'=>"{$gridName}_store",
			'columns' => $cols,
    		'dockedItems' => array(
    			array(
    				'xtype'=>'pagingtoolbar',
	        		'store'=> "{$gridName}_store",   
	        		'dock'=> 'bottom',
	        		'displayInfo'=> true
    				)   		
    		),
    		'features'=> array(
    			array('ftype'=> 'grouping'),
    			array('ftype'=> 'filters',),
    		)
		);
		if ($crud) {
			$addurl = array_key_exists('addurl', $gridConfig)? $gridConfig['addurl'] : "/{$controllername}/add/";
			$editurl = array_key_exists('editurl', $gridConfig)? $gridConfig['editurl'] : "/{$controllername}/edit/id/";
			$deleteurl = array_key_exists('deleteurl', $gridConfig) ? $gridConfig['deleteurl'] : "/{$controllername}/delete/id/";
			
			$gridConf['dockedItems'][] = array(
				'xtype'=>'toolbar',
				'dock'=>'top',
				'items'=>array(
					array(
						'text'=>'add',
						'iconCls'=>'icon-add',
						'handler'=>new Zend_Json_Expr("function(){window.location='{$addurl}';}")
					),
					array(
						'text'=>'edit',
						'iconCls'=>'icon-edit',
						'disabled'=>true,
						'id'=>'edit',
						'handler'=>new Zend_Json_Expr("function(){var record = {$gridName}.getSelectionModel().getSelection()[0]; window.location='{$editurl}' + record.get('id');}")
					),
					array(
						'text'=>'delete',
						'iconCls'=>'icon-delete',
						'disabled'=>true,
						'id'=>'delete',
						'handler'=>new Zend_Json_Expr("function(){var record = {$gridName}.getSelectionModel().getSelection()[0]; Ext.MessageBox.confirm('confirm','Are you sure to delete?', function(btn){ if (btn == 'yes'){Ext.Ajax.request({url: '{$deleteurl}' + record.get('id'),method: 'POST',success: function (response, options){ {$gridName}_store.load();},failure: function (response, options) {Ext.MessageBox.alert('fail', 'fail to delete:' + response.status);}});} else{return;}});}")
					)
				)
			);			
			
			$gridConf['listeners'] = array(
				'itemclick' => new Zend_Json_Expr("function(){Ext.getCmp('edit').enable();Ext.getCmp('delete').enable();}"),
				'itemdblclick'=>new Zend_Json_Expr("function(){var record = this.getSelectionModel().getSelection()[0]; window.location='{$editurl}' + record.get('id');}")
				);
		}
		
		//export
		$gridConf['dockedItems'][] = array(
			'xtype'=>'toolbar',
			'dock'=>'top',
			'items'=>array(
				array(
					'text'=>'export page',
					'iconCls'=>'icon-export-page',
					'handler'=>new Zend_Json_Expr("function(){window.location='{$storeUrl}/export/page/';}")
				),
				array(
					'text'=>'export all',
					'iconCls'=>'icon-export-all',
					'handler'=>new Zend_Json_Expr("function(){window.location='{$storeUrl}/export/all/';}")
				)
			)
		);
		
		$gridConf = array_merge($gridConf, $gridConfig);
		
		$grid = ZendX_Extjs_View_Helper_ExtComponent::js('Ext.grid.Panel', $gridConf, $gridName);
				
        $this->extjs->addOnLoad(Zend_Json::prettyPrint($grid, array("indent" => " ")));
	}
	
	private  function buildFields($qb)
	{
		$registry = Zend_Registry::getInstance();	
    	//$translator = $registry->Zend_Translate;
    	  
        $ast = $qb->getQuery ()->getAST ();
		
		//used for expressions without an identification variable
		$fieldNumber = 1;
		$returnFields = array ();
		foreach ( $ast->selectClause->selectExpressions as $selectExpression ) 
		{
			$expression = $selectExpression->expression;
			
			//if the expression is a string, there is an alias used to get all fields
			//to get all fields from the alias, we fetch the entity class and retrieve
			//the metadata from it, so we can set the fields correctly
			if (is_string ( $expression ))
			{
				//the expression itself is a pathexpression, where we can directly
				//fetch the title and field
				$alias = $expression;
				$tableName = $this->_getModelFromAlias ($alias,$qb);
				
				$returnFields = $this->_getColumns($tableName, $alias);				
			} 
			elseif ($expression instanceof AST\PathExpression) 
			{
				$field = ($selectExpression->fieldIdentificationVariable != null) ? $selectExpression->fieldIdentificationVariable : $expression->field;
				$tableName = $this->_getModelFromAlias ( $expression->identificationVariable,$qb );
				$returnFields[] = $this->_getColumns($tableName, $expression->identificationVariable, $expression->field, $field);
				
			} 
			elseif ($expression instanceof AST\Subselect) 
			{
				//handle subselects. we only need the identification variable for the field
				$field = $selectExpression->fieldIdentificationVariable;
				
				$title = ucwords ( str_replace ( '_', ' ', $field ) );
				
				$returnFields [$field]['title']  = $title;				
			} 
			else 
			{
				$field = $selectExpression->fieldIdentificationVariable;
				
				//doctrine uses numeric keys for expressions which got no
				//identification variable, so the key will be set to the
				//current counter $i
				if ($field === null) 
				{
					$field = $this->_getNameForExpression ( $expression );
					$key = $fieldNumber;
					$fieldNumber ++;
				} 
				else 
				{
					$key = $field;
				}
				
				$title = ucwords ( str_replace ( '_', ' ', $field ) );
				
				$returnFields [$key]['title']  = $title;				
			}
		}
				
		return $returnFields;  
	}
	
	private function _getColumns($model, $alias, $field = null, $dataIndex=null)
	{
		$registry = Zend_Registry::getInstance();	
    	//$translator = $registry->Zend_Translate;
    	
		$columns = array();
		$metadata = $registry->entityManager->getClassMetadata($model);
		foreach ( $metadata->fieldMappings as $key => $details )
		{
			if ($field != NULL && $key != $field) {
				continue;
			}
			$column = array();
			if (isset($details['options']) && isset($details['options']['render']) 
						&& !$details['options']['render'] && !isset($details['options']['grid'])) 
			{
				continue;;
			}	
			
			$column['text'] = $key;
			if ($field != null && $dataIndex != null) {
				$column['text'] = $dataIndex;
			}
			$column['dataIndex'] = $key;
			if ($field != null && $dataIndex != null) {
				$column['dataIndex'] = $dataIndex;
			}
			
			if (in_array($details ['type'], array('date','datetime')) ) {
				$column['renderer'] = "function(value){if(value)return value['date'];}";
			}
			
			$filtertype = "string";
			if (in_array($details ['type'], array('integer','smallint', 'bigint'))) {
				$filtertype = "int";
			}
			else if (in_array($details ['type'], array('float','decimal'))) {
				$filtertype = "float";
			}
			else if (in_array($details ['type'], array('date','datetime'))) {
				$filtertype = "date";
			}
			
			$column['filter'] = array(
				'type' => $filtertype,
				'dataIndex' =>"{$alias}.{$key}",
			);			
			
			if ($field != null) {
				return $column;
			}
			$columns[] = $column;
		}
        return $columns;
	}
	
	/**
     * Finds a model for which an alias belongs.
     *
     * @param string $alias
     * @return string The name of the entity.
     */
    private function _getModelFromAlias($alias, $qb)
    {        
        $fromParts = $qb->getDQLPart('from');

        //first try to get the model from the from part
        foreach ($fromParts as $fromPart) {
            if ($fromPart->getAlias() == $alias) {
                return $fromPart->getFrom();
            }
        }

        //when the from part doesnt have it, we first find the join field defined
        //by the alias
        $AST = $qb->getQuery()->getAST();

        $field = null;
        foreach ($AST->fromClause->identificationVariableDeclarations[0]->joinVariableDeclarations as $joinVariable) {
            if ($alias == $joinVariable->join->aliasIdentificationVariable) {
                $field = $joinVariable->join->joinAssociationPathExpression->associationField;
                break;
            }
        }
        if (is_null($field)) {
            throw new Exception("No field found.");
        }

        //iterate over the fromparts, get the metadata from it and
        //iterate then over the association mappings to find the specific
        //model for the alias
        $registry = Zend_Registry::getInstance();	
        foreach ($fromParts as $fromPart) {
            $metadata = $registry->entityManager->getClassMetadata($fromPart->getFrom());
            foreach ($metadata->associationMappings as $mapping) {
                if ($mapping['fieldName'] == $field) {
                    return $mapping['targetEntity'];
                }
            }
        }

        throw new Exception("No model found.");
    }
    
	/**
     * Generates the expression used in the select expression
     *
     * @param FunctionNode $expression
     * @return string
     */
    private function _getNameForExpression($expression)
    {
        $str = '';

        foreach ($expression as $key => $sub) {
            if ($sub instanceof AST\PathExpression) {
                $str .= $sub->identificationVariable . '.' . $sub->field;
                if ($expression instanceof Doctrine\ORM\Query\AST\Functions\FunctionNode) {
                    $str = $expression->name . '(' . $str . ')';
                } elseif ($expression instanceof AST\AggregateExpression) {
                    $str = $expression->functionName . '(' . $str . ')';
                }
                //when we got another array, we will call the method recursive and add
                //brackets for readability.
            } elseif (is_array($sub)) {
                $str .= '(' . $this->_getNameForExpression($sub) . ')';
                //call the method recursive to get all names.
            } elseif (is_object($sub)) {
                $str .= $this->_getNameForExpression($sub);
                //key is numeric and value is a string, we probably got an
                //arithmetic identifier (like "-" or "/")
            } elseif (is_numeric($key) && is_string($sub)) {
                $str .= ' ' . $sub . ' ';
                //we got a string value for example in an arithmetic expression
                //(a.value - 1) the "1" here is the value we append to the string here
            } elseif ($key == 'value') {
                $str .= $sub;
            }
        }

        return $str;
    }
}